package com.common.shiro.realm;

import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * 重写AuthorizingRealm，实现hasPermission支持复杂表达式（使用逆波兰表达式计算）
 *       organization:create OR organization:update OR organization:delete
 		 ( organization:create Or organization:update ) OR  NOT organization:delete
         ( organization:create && organization:update ) OR  ! organization:delete
         
 * @author limk
 * @date 2018年2月13日 下午12:16:18
 * @version 1.0
 */
public abstract class AbstractOperatorAuthorizingRealmWithRpn extends AuthorizingRealm {

	private static final Logger logger = LoggerFactory.getLogger(AbstractOperatorAuthorizingRealmWithRpn.class);
	
	private static final String K = " ";
	/**
	 * 支持的运算符和运算符优先级
	 */
	public static final Map<String, Integer> EXPMAP = new HashMap<String, Integer>() {
		/**
		* 
		*/
		private static final long serialVersionUID = 4808139774478181910L;

		{
			put("not", 0);
			put("!", 0);

			put("and", 0);
			put("&&", 0);

			put("or", 0);
			put("||", 0);

			put("(", 1);
			put(")", 1);
		}
	};

	public static final Set<String> EXPLIST = EXPMAP.keySet();

	public AbstractOperatorAuthorizingRealmWithRpn() {
	}

	public AbstractOperatorAuthorizingRealmWithRpn(CacheManager cacheManager) {
		super(cacheManager);
	}

	public AbstractOperatorAuthorizingRealmWithRpn(CredentialsMatcher matcher) {
		super(matcher);
	}

	public AbstractOperatorAuthorizingRealmWithRpn(CacheManager cacheManager, CredentialsMatcher matcher) {
		super(cacheManager, matcher);
	}

	@Override
	public boolean isPermitted(PrincipalCollection principals, String permission) {
		Stack<String> exp = getExp(EXPLIST, permission);
		if (exp.size() == 1) {
			return super.isPermitted(principals, exp.pop());
		}
		List<String> expTemp = new ArrayList<>();
		// 将其中的权限字符串解析成true , false
		for (String temp : exp) {
			if (EXPLIST.contains(temp)) {
				expTemp.add(temp);
			} else {
				expTemp.add(Boolean.toString(super.isPermitted(principals, temp)));
			}
		}
		logger.debug("permission:{}  Rpn:{}  parse:{}", permission, exp, expTemp);
		// 计算逆波兰
		return computeRpn(EXPLIST, expTemp);
	}

	private static boolean computeRpn(Collection<String> expList, Collection<String> exp) {
		logger.debug("RPN  exp :{}", exp);
		Stack<Boolean> stack = new Stack<>();
		for (String temp : exp) {
			if (expList.contains(temp)) {
				if ("!".equals(temp) || "not".equals(temp)) {
					stack.push(!stack.pop());
				} else if ("and".equals(temp) || "&&".equals(temp)) {
					Boolean s1 = stack.pop();
					Boolean s2 = stack.pop();
					stack.push(s1 && s2);
				} else {
					Boolean s1 = stack.pop();
					Boolean s2 = stack.pop();
					stack.push(s1 || s2);
				}
			} else {
				stack.push(Boolean.parseBoolean(temp));
			}
		}
		if (stack.size() > 1) {
			logger.error("computeRpn RESULT ERROR>{}  exp:{}", stack, exp);
			throw new RuntimeException("compute error！ stack: " + exp.toString());
		} else {
			logger.debug("computeRpn RESULT SUCCESS>{}", stack);
			return stack.pop();
		}
	}

	/**
	 * 获得逆波兰表达式
	 * 
	 * @param expList
	 * @param exp
	 * @return
	 */
	private static Stack<String> getExp(Collection<String> expList, String exp) {
		Stack<String> s1 = new Stack<>();
		Stack<String> s2 = new Stack<>();
		for (String str : exp.split(K)) {
			str = str.trim();
			String strL = str.toLowerCase();
			if ("".equals(str)) {
				continue;
			}
			if ("(".equals(str)) {
				// 左括号
				s1.push(str);
			} else if (")".equals(str)) {
				// 右括号
				while (!s1.empty()) {
					String temp = s1.pop();
					if ("(".equals(temp)) {
						break;
					} else {
						s2.push(temp);
					}
				}
			} else if (expList.contains(strL)) {
				// 操作符
				if (s1.empty()) {
					s1.push(strL);
				} else {
					String temp = s1.peek();
					if ("(".equals(temp) || ")".equals(temp)) {
						s1.push(strL);
					} else if (EXPMAP.get(strL) >= EXPMAP.get(temp)) {
						s1.push(strL);
					} else {
						s2.push(s1.pop());
						s1.push(strL);
					}
				}
			} else {
				// 运算数
				s2.push(str);
			}
		}
		while (!s1.empty()) {
			s2.push(s1.pop());
		}
		return s2;
	}
}
